package com.zxy.supplier_system.server.configuration

import com.zxy.supplier_system.server.entity.Employee
import com.zxy.supplier_system.server.entity.JwtTokenMetadata
import com.zxy.supplier_system.server.service.AuthorizationService
import com.zxy.supplier_system.server.service.EmployeeService
import com.zxy.supplier_system.server.utils.LoggerDelegate
import com.zxy.supplier_system.server.utils.parseToClaims
import com.zxy.supplier_system.server.xinyuan.configuration.AdminUsernameProperties
import com.zxy.supplier_system.server.xinyuan.configuration.BasicTokenAuthorizationFilter
import io.jsonwebtoken.ExpiredJwtException
import io.jsonwebtoken.JwtException
import jakarta.servlet.FilterChain
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpMethod
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.config.Customizer
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.builders.WebSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer
import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher
import org.springframework.security.web.util.matcher.AntPathRequestMatcher
import org.springframework.stereotype.Component
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.filter.OncePerRequestFilter
import org.springframework.web.servlet.handler.HandlerMappingIntrospector
import java.security.Principal
import java.time.Duration
import java.time.OffsetDateTime
import java.time.ZoneId
import java.time.temporal.ChronoUnit
import kotlin.properties.Delegates

const val AUTHORIZATION_HEADER_VALUE_PREFIX = "Bearer "

@ConfigurationProperties(prefix = "jwt")
@Component
class JwtProperties {
    /**
     * Jwt token 加密的密钥
     */
    lateinit var secret: String

    /**
     * Jwt token 过期的时间
     */
    var expiration by Delegates.notNull<Long>()

    /**
     * [expiration]的单位
     */
    var expirationTimeUnit: ChronoUnit = ChronoUnit.DAYS
}

@EnableWebSecurity
@Configuration
@EnableMethodSecurity(prePostEnabled = true)
class EmployeeSecurityConfiguration(
    private val authHeaderValidator: AuthHeaderValidator,
    private val adminUsernameProperties: AdminUsernameProperties,
) {

    @Bean
    fun securityFilterChain(http: HttpSecurity, introspector: HandlerMappingIntrospector): SecurityFilterChain {
        val requestMatcherBuilder = MvcRequestMatcher.Builder(introspector)
        http
            .authorizeHttpRequests { requests ->
                requests
                    .requestMatchers("/xinyuan/logIn").permitAll()
            }
            .httpBasic(Customizer.withDefaults())

        http
            .authorizeHttpRequests {
                it
                    .requestMatchers(AntPathRequestMatcher.antMatcher("/h2-console/**")).permitAll()
                    .requestMatchers(requestMatcherBuilder.pattern(HttpMethod.PUT, "/authorization/logIn")).permitAll()
                    .requestMatchers(
                        requestMatcherBuilder.pattern(HttpMethod.GET, "/systemServices/current"),
                        requestMatcherBuilder.pattern(HttpMethod.GET, "/systemServices/current/setting")
                    ).permitAll()
                    .requestMatchers("/wechat/official/message/receive").permitAll()
                    .requestMatchers(requestMatcherBuilder.pattern("/error")).permitAll()
                    .requestMatchers("/voices/**").permitAll()
                    .anyRequest().authenticated()
            }
            .sessionManagement {
                it.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            }


        http
            .csrf {
                it.ignoringRequestMatchers(AntPathRequestMatcher.antMatcher("/h2-console/**"))
                it.disable()
            }
            .cors {

            }

        http.addFilterBefore(
            JwtTokenAuthenticationFilter(authHeaderValidator), UsernamePasswordAuthenticationFilter::class.java
        )

        http.addFilterBefore(
            BasicTokenAuthorizationFilter(adminUsernameProperties.username, adminUsernameProperties.password),
            UsernamePasswordAuthenticationFilter::class.java
        )

        return http.build()
    }

    @Bean
    fun configureWebSecurity(): WebSecurityCustomizer {
        return object : WebSecurityCustomizer {
            override fun customize(web: WebSecurity) {
                web.ignoring().requestMatchers("/voices/**")
            }

        }
    }

}

class JwtTokenAuthenticationFilter(
    private val authHeaderValidator: AuthHeaderValidator
) : OncePerRequestFilter() {

    companion object {
        private val log by LoggerDelegate()
    }


    override fun doFilterInternal(
        request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain
    ) {
        log.debug("校验JwtToken，注入AuthenticationToken到上下文中，url: {}", request.requestURL)
        val authHeader = request.getHeader(HttpHeaders.AUTHORIZATION)
        if (authHeader != null) {
            try {
                this.authHeaderValidator.validate(authHeader)
            } catch (e: JwtException) {
                log.error("验证token失败", e)
            } catch (e: Exception) {
                log.error("验证token失败", e)
            } finally {
                filterChain.doFilter(request, response)
            }
        } else {
            filterChain.doFilter(request, response)
        }
    }

}

@Service
class AuthHeaderValidator(
    private val employeeService: EmployeeService,
    private val jwtProperties: JwtProperties
) {

    companion object {
        private val log by LoggerDelegate()
    }

    @Transactional(readOnly = true)
    fun validate(authHeader: String?) {
        if (authHeader != null && authHeader.startsWith(AUTHORIZATION_HEADER_VALUE_PREFIX)) {
            val token = authHeader.substring(AUTHORIZATION_HEADER_VALUE_PREFIX.length)
            val claims = try {
                parseToClaims(token, jwtProperties.secret)
            } catch (e: ExpiredJwtException) {
                return
            }
            val username = claims.subject
            val userDetails = employeeService.loadUserByUsername(username)
            if (!userDetails.isEnabled){
                log.warn("用户 {} {} 账户已被禁用，拒绝访问", username,userDetails.name)
                return
            }

            // 校验jwt claims

            // 将token注入上下文
            val usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken(
                userDetails, null, userDetails.authorities
            )
            val client = JwtTokenMetadata.Client.valueOf(
                claims.get(
                    AuthorizationService.JWT_CLAIM_CLIENT_TYPE_KEY,
                    String::class.java
                ) ?: return
            )
            val jwtTokenMetadata = userDetails.jwtTokenMetadataList.find {
                it.client == client
            } ?: return
            val issuedAt = claims.issuedAt.toInstant()
                .atZone(ZoneId.systemDefault())
                .toOffsetDateTime()
            val lastRefreshDateTime = jwtTokenMetadata.lastRefreshDateTime ?: issuedAt ?: return
            val duration = Duration.of(jwtProperties.expiration, jwtProperties.expirationTimeUnit)
            if (lastRefreshDateTime.plus(duration) < OffsetDateTime.now()) {
                return
            }

            val lastLogOutDateTime = jwtTokenMetadata.lastLogOutDateTime
            if (lastLogOutDateTime != null && issuedAt.isBefore(lastLogOutDateTime)) {
                return
            }

            // lastLogInDateTime 带毫秒 所以 + 1 秒用于对比
            if (issuedAt.isBefore(jwtTokenMetadata.lastLogInDateTime.minusSeconds(1))) {
                return
            }


            usernamePasswordAuthenticationToken.details = userDetails
            SecurityContextHolder.getContext().authentication = usernamePasswordAuthenticationToken
            log.debug("保存token到Security上下文")
        }
    }
}


val Principal.employee: Employee?
    get() = (this as? UsernamePasswordAuthenticationToken)?.principal as? Employee